﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Excel = Microsoft.Office.Interop.Excel;

namespace Mini_Project
{
    class helper
    {
        RecommenderSystem r;
        public algorithms a;

        public Dictionary<double, user> users;
        public Dictionary<double, item> items;

        // members for graphs
        object misValue = System.Reflection.Missing.Value;
        Excel.Application excelApp;

        Boolean opened; // checks if the excel app was already opened

        public List<double> allItems;

        public helper(RecommenderSystem s)
        {
            this.r = s;
            users = new Dictionary<double, user>();
            items = new Dictionary<double, item>();
            allItems = new List<double>();
        }

        public void find_all_items()
        {
            int i;

            List<pair> itemRatings = new List<pair>();
            foreach (KeyValuePair<double, List<pair>> rating in r.itemKey)
            {
                itemRatings.Add(new pair(rating.Key, rating.Value.Count));
            }
            IEnumerable<pair> sortedItems = itemRatings.OrderByDescending(key => key.getSecond());

            for (i = 0; i < r.itemKey.Count; i++)
            {
                allItems.Add(sortedItems.ElementAt(i).getFirst());
            }
        }

        public void trainTest(double p) // p = percentage in training set
        {
            this.r.trainUser_2 = new Dictionary<double, List<pair>>();     // user, item, rating
            this.r.testUser_2 = new Dictionary<double, List<pair>>();      // user, item, rating
            this.r.trainItem_2 = new Dictionary<double, List<pair>>();     // user, item, rating
            this.r.testItem_2 = new Dictionary<double, List<pair>>();      // user, item, rating

            this.r.wau_cosine_train = new Dictionary<KeyValuePair<int, int>, double>();
            this.r.wau_pearson_train = new Dictionary<KeyValuePair<int, int>, double>();

            int amount = 0;
            Random r = new Random();
            while (amount < (this.r.totalLines - (this.r.totalLines * p)))
            {
                int randUser = r.Next(1, (this.r.userKey.Count + 1));
                while ((!(this.r.userKey.ContainsKey(randUser))) || (this.r.trainUser_2.ContainsKey(randUser)))  // if this user doesnt exist,
                    randUser = ((randUser + 1) % (this.r.userKey.Count));                               // or was already chosen, pick a new one
                Random r1 = new Random();
                List<pair> ratings = this.r.userKey[randUser];
                int max = (ratings.Count);
                int randK = r1.Next(1, max);
                Random r2 = new Random();
                List<pair> addToTrain = new List<pair>();
                List<pair> addToItemTrain = new List<pair>();
                for (int j = 0; j < randK; j++)         // add k random ratings to the training set
                {
                    int itemIndex = r2.Next(1, max);
                    itemIndex -= 1;
                    double itemValue = ratings[itemIndex].getFirst();
                    while (addToTrain.Contains(ratings[itemIndex]))
                    {
                        itemIndex = ((itemIndex + 1) % (ratings.Count));
                    }
                    addToTrain.Add(ratings[itemIndex]);
                    if (this.r.trainItem_2.ContainsKey(itemValue))
                        addToItemTrain = this.r.trainItem_2[itemValue];
                    else
                    {
                        addToItemTrain = new List<pair>();
                    }
                    pair toAdd = new pair(randUser, (ratings[itemIndex].getSecond()));
                    addToItemTrain.Add(toAdd);
                    this.r.trainItem_2[itemValue] = addToItemTrain;
                }
                this.r.trainUser_2.Add(randUser, addToTrain);
                List<pair> addToTest = new List<pair>();
                List<pair> addToItemTest = new List<pair>();
                for (int i = 0; i < ratings.Count; i++)     // add the rest of the ratings to the testing set
                {
                    if (!(addToTrain.Contains(ratings[i])))
                    {
                        addToTest.Add(ratings[i]);
                        double itemValue = ratings[i].getFirst();   // item number
                        if (this.r.testItem_2.ContainsKey(itemValue))
                            addToItemTest = this.r.testItem_2[itemValue];
                        else
                        {
                            addToItemTest = new List<pair>();
                        }
                        pair toAdd = new pair(randUser, (ratings[i].getSecond()));
                        addToItemTest.Add(toAdd);
                        this.r.testItem_2[itemValue] = addToItemTest;
                    }
                }
                this.r.testUser_2.Add(randUser, addToTest);
                amount = amount + (ratings.Count - randK);

            }
            for (int i = 0; i < this.r.userKey.Count; i++)         // add the rest of the users to the training set
            {
                List<pair> addToItemTrain = new List<pair>();
                if ((!(this.r.trainUser_2.ContainsKey(i))) && (!(this.r.testUser_2.ContainsKey(i))) && (this.r.userKey.ContainsKey(i)))
                {
                    this.r.trainUser_2.Add(i, this.r.userKey[i]);
                    List<pair> ratings = this.r.userKey[i];
                    for (int j = 0; j < ratings.Count; j++)
                    {
                        double itemValue = ratings[j].getFirst();   // item number
                        if (this.r.trainItem_2.ContainsKey(itemValue))
                            addToItemTrain = this.r.trainItem_2[itemValue];
                        else
                        {
                            addToItemTrain = new List<pair>();
                        }
                        pair toAdd = new pair(i, (ratings[j].getSecond()));
                        addToItemTrain.Add(toAdd);
                        this.r.trainItem_2[itemValue] = addToItemTrain;
                    }
                }
            }
        }


        public void calcAllUserProfiles(){
            double[] temp=new double[19];
            item i;
            user u;
            double total;
            for (int k=1; k<=users.Count; k++){
                total = 0;
                temp = new double[19];
                List<pair> ratings = r.userData[k];
                foreach (pair rating in ratings)
                {
                    i = items[rating.getFirst()];
                    for (int m = 0; m < 19; m++)
                    {
                        if (i.genres[m] != 0)
                        {
                            total++;
                            temp[m] += i.genres[m];
                        }
                    }
                }
                // normalizing profile
                for (int m = 0; m < 19; m++)
                    temp[m]=temp[m]/total;
                u = users[k];
                u.setProfile(temp);
                users[k] = u;
             
            }

        }

        // x-axis: recall
        // y-axis: precision
        // different points for different lengths
        // results: length -> algo -> precision -> recall
        public void GraphDifferentLengths(Dictionary<int, Dictionary<string, Dictionary<string, double>>> results)
        {
            try
            {
               if (!opened)
               {
                   excelApp = new Excel.Application();
                   excelApp.Visible = true;
                   opened = true;
               }
                
                Excel.Workbook workbook = excelApp.Workbooks.Add(1);
                Excel.Worksheet worksheet = (Excel.Worksheet)workbook.ActiveSheet;

                Dictionary<string, Dictionary<string, double>> algos = new Dictionary<string, Dictionary<string, double>>();
                foreach (KeyValuePair<int, Dictionary<string, Dictionary<string, double>>> r in results)
                {
                    algos = r.Value;
                }

                int column = 0, row = 1;
                int numAlgos = 0;

                // every algo has 2 columns - first is precision, second is recall
                foreach (KeyValuePair<int, Dictionary<string, Dictionary<string, double>>> len in results)
                {
                    row++;
                    column = 1;
                    if (numAlgos == 0)
                        numAlgos = len.Value.Count;
                    foreach (KeyValuePair<string, Dictionary<string, double>> algo in len.Value)
                    {
                        foreach (KeyValuePair<string, double> p in algo.Value)
                        {
                            column++;
                            worksheet.Cells[row, column] = p.Value;
                        }
                    }
                }

                row = 1;
                column = 2;
                foreach (KeyValuePair<string, Dictionary<string, double>> a in algos)
                {
                    foreach (KeyValuePair<string, double> prec in a.Value)
                    {
                        worksheet.Cells[row, column] = a.Key + " " + prec.Key;
                        column++;
                    }
                }

                worksheet.Cells[1, 1] = "length";

                column = 1;
                row++;
                foreach (KeyValuePair<int, Dictionary<string, Dictionary<string, double>>> num in results)
                {
                    worksheet.Cells[row, column] = num.Key;
                    row++;
                }

                Excel.ChartObjects xlCharts = (Excel.ChartObjects)worksheet.ChartObjects(Type.Missing);
                Excel.ChartObject myChart = (Excel.ChartObject)xlCharts.Add(10, 80, 300, 250);
                Excel.Chart chartPage = myChart.Chart;
                var seriesCol = chartPage.SeriesCollection(misValue);
                chartPage.ChartType = Excel.XlChartType.xlXYScatterLinesNoMarkers;

                char cColumn = 'A';
                String sRow = "2";
                String col;
                Excel.Range chartRange1, chartRange2;
                int max = results.Count + 1, i = 0;
                String sMax = max.ToString();
                foreach (KeyValuePair<string, Dictionary<string, double>> a in algos)
                {
                    cColumn++;
                    col = cColumn.ToString();
                    chartRange1 = worksheet.Range[col + sRow, col + sMax];
                    cColumn++;
                    col = cColumn.ToString();
                    chartRange2 = worksheet.Range[col + sRow, col + sMax];
                    seriesCol.add(chartRange1, misValue, misValue, misValue, misValue);
                    seriesCol(i + 1).Name = a.Key;
                    seriesCol(i + 1).XValues = chartRange2;
                    i++;
                }

                string path = Directory.GetCurrentDirectory();
                workbook.SaveAs(path + "/precisionRecallDifferentLengths.xls", Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
                excelApp.Quit();

                dump(worksheet);
                dump(workbook);
                dump(excelApp);


            }
            catch (Exception theException)
            {
                Console.WriteLine("exception");
                String errorMessage;
                errorMessage = "Error: ";
                errorMessage = String.Concat(errorMessage, theException.Message);
                errorMessage = String.Concat(errorMessage, " Line: ");
                errorMessage = String.Concat(errorMessage, theException.Source);
            }

        }
        
        // x-axis: recall
        // y-axis: precision
        // different points for different lengths
        // results: percent -> algo -> precision -> recall
        public void GraphDifferentParams(Dictionary<double, Dictionary<string, Dictionary<string, double>>> results, String param)
        {
            try
            {
                if (!opened)
                {
                    excelApp = new Excel.Application();
                    excelApp.Visible = true;
                    opened = true;
                }
                 
                Excel.Workbook workbook = excelApp.Workbooks.Add(1);
                Excel.Worksheet worksheet = (Excel.Worksheet)workbook.ActiveSheet;

                Dictionary<string, Dictionary<string, double>> algos = new Dictionary<string, Dictionary<string, double>>();
                foreach (KeyValuePair<double, Dictionary<string, Dictionary<string, double>>> r in results)
                {
                    algos = r.Value;
                }

                int column = 0, row = 1;
                int numAlgos = 0;

                // every algo has 2 columns - first is precision, second is recall
                foreach (KeyValuePair<double, Dictionary<string, Dictionary<string, double>>> percent in results)
                {
                    row++;
                    column = 1;
                    if (numAlgos == 0)
                        numAlgos = percent.Value.Count;
                    foreach (KeyValuePair<string, Dictionary<string, double>> algo in percent.Value)
                    {
                        foreach (KeyValuePair<string, double> p in algo.Value)
                        {
                            column++;
                            worksheet.Cells[row, column] = p.Value;
                        }
                    }
                }

                row = 1;
                column = 2;
                foreach (KeyValuePair<string, Dictionary<string, double>> a in algos)
                {
                    foreach (KeyValuePair<string, double> prec in a.Value)
                    {
                        worksheet.Cells[row, column] = a.Key + " " + prec.Key;
                        column++;
                    }
                }

                worksheet.Cells[1, 1] = param;

                column = 1;
                row++;
                foreach (KeyValuePair<double, Dictionary<string, Dictionary<string, double>>> num in results)
                {
                    worksheet.Cells[row, column] = num.Key;
                    row++;
                }

                Excel.ChartObjects xlCharts = (Excel.ChartObjects)worksheet.ChartObjects(Type.Missing);
                Excel.ChartObject myChart = (Excel.ChartObject)xlCharts.Add(10, 80, 300, 250);
                Excel.Chart chartPage = myChart.Chart;
                var seriesCol = chartPage.SeriesCollection(misValue);
                chartPage.ChartType = Excel.XlChartType.xlXYScatterLinesNoMarkers;

                char cColumn = 'A';
                String sRow = "2";
                String col;
                Excel.Range chartRange1, chartRange2;
                int max = results.Count + 1, i = 0;
                String sMax = max.ToString();
                foreach (KeyValuePair<string, Dictionary<string, double>> a in algos)
                {
                    cColumn++;
                    col = cColumn.ToString();
                    chartRange1 = worksheet.Range[col + sRow, col + sMax];
                    cColumn++;
                    col = cColumn.ToString();
                    chartRange2 = worksheet.Range[col + sRow, col + sMax];
                    seriesCol.add(chartRange1, misValue, misValue, misValue, misValue);
                    seriesCol(i + 1).Name = a.Key;
                    seriesCol(i + 1).XValues = chartRange2;
                    i++;
                }

                string path = Directory.GetCurrentDirectory();
                workbook.SaveAs(path + "/precisionRecallDifferent"+param+".xls", Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
                excelApp.Quit();

                dump(worksheet);
                dump(workbook);
                dump(excelApp);


            }
            catch (Exception theException)
            {
                Console.WriteLine("exception");
                String errorMessage;
                errorMessage = "Error: ";
                errorMessage = String.Concat(errorMessage, theException.Message);
                errorMessage = String.Concat(errorMessage, " Line: ");
                errorMessage = String.Concat(errorMessage, theException.Source);
            }

        }

        private void dump(object obj)
        {
            try
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
                obj = null;
            }
            catch (Exception ex)
            {
                obj = null;
                Console.WriteLine(ex);
            }
            finally
            {
                GC.Collect();
            }
        }

        public List<string> RecommendPredictions(string sAlgorithm, string sUserId, int cRecommendations)
        {
            int i, n = 0;
            List<string> ans = new List<string>();
            List<pair> uRatings = r.userKey[Convert.ToDouble(sUserId)];
            List<double> uItems = new List<double>();
            IEnumerable<double> diff;
            Dictionary<double, double> itemPredictions = new Dictionary<double, double>();  /*<double, double> = <item, prediction>*/
            IOrderedEnumerable<KeyValuePair<double, double>> sortedItems;

            foreach (pair rating in uRatings)
            {
                uItems.Add(rating.getFirst());
            }

            diff = allItems.Except(uItems);

            /* get predictions of all items not rated by user */
            foreach (double item in diff)
            {
                itemPredictions.Add(item, r.PredictRating(sAlgorithm, sUserId, item.ToString()));
            }
            sortedItems = itemPredictions.OrderByDescending(key => key.Value);

            /* find top n recommendations */
            for (i = 0; i < sortedItems.Count() && n < cRecommendations; i++)
            {

                if (sortedItems.ElementAt(i).Value != 5)
                {
                    ans.Add(sortedItems.ElementAt(i).Key.ToString());
                    n++;
                }
            }

            return ans;
        }

        public double getAverageUserRating(string uid)
        {
            Dictionary<double, int> dAllRatings_a = r.GetRatingsHistogram(uid);
            double ra = 0, totalRatings = 0;

            foreach (KeyValuePair<double, int> p in dAllRatings_a)
            {
                ra += (p.Key * p.Value);
                totalRatings += p.Value;
            }
            return (ra / totalRatings);
        }


        IOrderedEnumerable<KeyValuePair<double, double>> findAllUserWeights(string sAlgorithm, string sUserId)
        {
            double weight;
            Dictionary<double, double> userWeights = new Dictionary<double, double>(); /* <double, double> = <user, weight> */
            KeyValuePair<int, int> users;

            if (sAlgorithm.CompareTo("NNPearson") == 0)
            {
                foreach (KeyValuePair<double, List<pair>> rating in r.userKey)
                {
                    if (rating.Key != Convert.ToDouble(sUserId))
                    {
                        users = new KeyValuePair<int, int>(Convert.ToInt16(sUserId), Convert.ToInt16(rating.Key));
                        if (r.wau_pearson.ContainsKey(users))
                        {
                            weight = r.wau_pearson[users];
                        }
                        else
                        {
                            weight = a.calc_wau_pearson(Convert.ToDouble(sUserId), rating.Key, getAverageUserRating(rating.Key.ToString()));
                        }
                        userWeights.Add(rating.Key, weight);
                    }
                }
            }
            else if (sAlgorithm.CompareTo("NNCosine") == 0)
            {
                foreach (KeyValuePair<double, List<pair>> rating in r.userKey)
                {
                    if (rating.Key != Convert.ToDouble(sUserId))
                    {
                        users = new KeyValuePair<int, int>(Convert.ToInt16(sUserId), Convert.ToInt16(rating.Key));
                        if (r.wau_cosine.ContainsKey(users))
                        {
                            weight = r.wau_cosine[users];
                        }
                        else
                        {
                            weight = a.calc_wau_cosine(Convert.ToDouble(sUserId), rating.Key);
                        }
                        userWeights.Add(rating.Key, weight);
                    }
                }
            }
            else
            {
                foreach (KeyValuePair<double, List<pair>> rating in r.userKey)
                {
                    if (rating.Key != Convert.ToDouble(sUserId))
                    {
                        users = new KeyValuePair<int, int>(Convert.ToInt16(sUserId), Convert.ToInt16(rating.Key));
                        if (r.wau_SVD.ContainsKey(users))
                        {
                            weight = r.wau_SVD[users];
                        }
                        else
                        {
                            weight = a.calc_wau_SVD(Convert.ToDouble(sUserId), rating.Key);
                        }
                        userWeights.Add(rating.Key, weight);
                    }
                }
            }

            return userWeights.OrderByDescending(key => key.Value);
        }

        public List<string> RecommendNN(string sAlgorithm, string sUserId, int cRecommendations)
        {
            int i, n = 0;
            double iid, uid, weight, total = 0;
            List<pair> tempUserItems, uRatings = r.userKey[Convert.ToDouble(sUserId)];
            List<double> uItems = new List<double>();
            List<string> ans = new List<string>();
            IOrderedEnumerable<KeyValuePair<double, double>> sortedWeights; /* <double, double> = <user, weight> */
            IOrderedEnumerable<KeyValuePair<double, double>> sortedItems; /* <double, double> = <item, totalWeight> */
            Dictionary<double, double> itemWeights = new Dictionary<double, double>(); /* <double, double> = <item, totalWeight> */

            foreach (pair rating in uRatings)
            {
                uItems.Add(rating.getFirst());
            }


            sortedWeights = findAllUserWeights(sAlgorithm, sUserId);

            /* look at top 20 users */
            for (i = 0; i < sortedWeights.Count() && i < 20; i++)
            {
                uid = sortedWeights.ElementAt(i).Key;
                weight = sortedWeights.ElementAt(i).Value;
                tempUserItems = r.userKey[uid];        /* find all items user rated */
                foreach (pair itemRating in tempUserItems)
                {
                    /* add weight of user to item */
                    iid = itemRating.getFirst();
                    if (itemWeights.ContainsKey(iid))
                    {
                        itemWeights[iid] += weight;
                    }
                    else
                    {
                        itemWeights.Add(iid, weight);
                    }
                    total += weight;
                }
            }

            /* normalize weights */
            for (i = 0; i < itemWeights.Count; i++)
            {
                itemWeights[itemWeights.ElementAt(i).Key] = (itemWeights[itemWeights.ElementAt(i).Key] / total);
            }

            sortedItems = itemWeights.OrderByDescending(key => key.Value);

            for (i = 0; n < cRecommendations && i < sortedItems.Count(); i++)
            {
                if (!uItems.Contains(sortedItems.ElementAt(i).Key))
                {
                    ans.Add(sortedItems.ElementAt(i).Key.ToString());
                    n++;
                }
            }

            return ans;
        }

        public List<string> RecommendPopularity(string sUserId, int cRecommendations)
        {
            int added = 0;
            double UID = Convert.ToDouble(sUserId);
            List<String> recs = new List<String>();
            if (r.userKey.ContainsKey(UID))
            {
                List<pair> userRatings = r.userKey[UID];
                for (int item = 0; (item < allItems.Count) & added < cRecommendations; item++)
                {
                    if (!(ContainsFirst(userRatings, allItems[item])))
                    {
                        recs.Add(Convert.ToString(allItems[item]));
                        added++;
                    }
                }
            }
            else
            {
                Console.WriteLine("user was not in training set");
            }
            return recs;
        }

        public Boolean ContainsFirst(List<pair> list, double first)
        {
            foreach (pair p in list)
            {
                if (p.getFirst() == first)
                    return true;
            }
            return false;
        }

        public List<string> RecommendCP(String sUserId, int cRecommendations)
        {
            int added = 0;
            List<String> recs = new List<String>();
            List<pair> CP = new List<pair>();
            // is user has more than 1 rating do max
            List<pair> userRatings = r.userKey[Convert.ToDouble(sUserId)];
            // if the user has no ratings will send to popularity
            if (userRatings.Count == 0)
            {
                Console.WriteLine("shouldnt be here - user has no ratings");
                RecommendPopularity(sUserId, cRecommendations);
            }
            else if (userRatings.Count == 1)
            {
                CP = a.CalculateItemCP(userRatings[0].getFirst());
                for (int item = 0; (item < CP.Count) & added < cRecommendations; item++)
                {
                    if (!(ContainsFirst(userRatings, CP[item].getFirst())))
                    {
                        recs.Add(Convert.ToString(allItems[item]));
                        added++;
                    }
                }
            }
            else
            {
                // double item, double max conditional probability for that item based on this users ratings
                Dictionary<double, double> probabilities = new Dictionary<double, double>();
                foreach (pair p in userRatings)
                {
                    CP = a.CalculateItemCP(p.getFirst());
                    foreach (pair prob in CP)
                    {
                        double itemNum = prob.getFirst();
                        if (probabilities.ContainsKey(itemNum))
                            probabilities[itemNum] = Math.Max(probabilities[itemNum], prob.getSecond());
                        else
                        {
                            if (!(ContainsFirst(userRatings, itemNum)))
                                probabilities.Add(itemNum, prob.getSecond());
                        }
                    }
                }
                // sort probabilities dictionary by value
                probabilities = probabilities.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
                foreach (KeyValuePair<double, double> p in probabilities)
                {
                    if (!(ContainsFirst(userRatings, p.Key)))
                    {
                        if (added < cRecommendations)
                        {
                            recs.Add(Convert.ToString(p.Key));
                            added++;
                        }
                    }
                }
            }
            return recs;
        }




        public double NoveltyDistance(item it ,double[] novelty_profile)
        {
            double distance = 0;
            int total = 0;
            double[] item_content = it.genres;
            for (int i = 0; i < it.getCONTENT_SIZE(); i++)
            {
                distance += Math.Abs(item_content[i] - novelty_profile[i]);
                if (item_content[i]!=0 || novelty_profile[i]!=0) {
                    total++;
                }
            }

            return (distance/(total/2));
        }


    }

}
